/*==============================================================================
            Copyright (c) 2013 Moritz Hader, Marcel Schrder
            All Rights Reserved.

	@file  ShaderManagementUnit.h

	@brief This header provides the class-definitions as well as the shaders
	used by the ShaderManagementUnit. General purpose of this class is to provide
	an easy to use interface to initialize, load and use GLSL programs.
   
   This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
==============================================================================*/

#include <egl/ShaderManagementUnit.h>

#include <stdarg.h>
#include <android/log.h>
#include <egl/HeimdallGLUtilities.h>

ShaderManagementUnit::ShaderManagementUnit(){
	//Within the constructor of the ShaderManagementUnit, we initialize the array
	//that holds the handles to the different Shader-Programs

	for(int i = 0; i< HD_SHADER_ENDLIST;i++){
		shaderHandles[i] = 0;
	}
}

ShaderManagementUnit::~ShaderManagementUnit(){
	//Like in the constructor, we iterate over the array and delete the GLSL-programm
	for(int i = 0; i< HD_SHADER_ENDLIST;i++){
		glDeleteProgram(shaderHandles[i]);
	}
}

/**
 * Loads the given shader and stores the key at the ShaderProgIndex position in the array.
 *
 * The optional parameters must always be <HDAttributeTypes,String> where the string names the attribute.
 * Every optional parameter-list must be terminated by HD_ATTRIBUTES_ENDLIST.
 */
void ShaderManagementUnit::LoadShader(HDProgramTypes shaderProgIndex, const char* vertexShader, const char* fragmentShader,...){

	//Make some handles to the vertex and fragment
	GLuint vertHandle = 0;
	GLuint fragHandle = 0;
	GLuint shaderProgID = 0; //Handle to the shader program

	//Call the GL library to create an object for fragment and vertex shader
	vertHandle = glCreateShader(GL_VERTEX_SHADER);
	fragHandle = glCreateShader(GL_FRAGMENT_SHADER);

	//Call the GL library to load the shader-programm into the previously created shader object
	glShaderSource(vertHandle, 1, &vertexShader, NULL);
	glShaderSource(fragHandle, 1, &fragmentShader, NULL);

	//Compile the shit
	glCompileShader(vertHandle);
	glCompileShader(fragHandle);

		//Check for errors during the shader-creation
		if(CheckShadersForError(fragHandle) == false || CheckShadersForError(vertHandle) == false ){
			//Clean up
			glDeleteShader(vertHandle);
			glDeleteShader(fragHandle);
			return;
		}else{
			__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Shaders successfully compiled.");
		}

	//Create an GL-internal program-object and receive the handle
	shaderProgID = glCreateProgram();

		//Attach the shaders to the program
		glAttachShader(shaderProgID, vertHandle);
		glAttachShader(shaderProgID, fragHandle);

	//To be able to access the attributes and uniform of the shader-program, we will need to create an attribute-handle within the memory of the GL-program
	//One might have noticed the three dots in the parameters. These dots denote a variable list of parameters and are accessed via the va_list object

	va_list optAttr; //object that will yield access to the optional parameters
		int identifier;
		char* attributeName;

	va_start(optAttr,fragmentShader);

		while((identifier = va_arg(optAttr, int)) != HD_ATTRIBUTES_ENDLIST){

			//Get the Attribute-Name
			attributeName = va_arg(optAttr, char*);
			__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Extracting Attribute %d with key %s",identifier,attributeName);
			//Bind the attribute-identifier to the name
			glBindAttribLocation(shaderProgID, identifier,attributeName);
		};

	va_end(optAttr);

	//The attribute handles are created, now the program must be linked together
	glLinkProgram(shaderProgID);

		if(CheckProgrammForError(shaderProgID)==false){
			//Clean up
			glDeleteShader(vertHandle);
			glDeleteShader(fragHandle);
			return;
		}else{
			__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Shaders successfully linked.");
		}

	//Clean up
	glDeleteShader(vertHandle);
	glDeleteShader(fragHandle);

	//Store the Shader-Handle in the array
	shaderHandles[shaderProgIndex] = shaderProgID;

	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Done loading the shader-program.");
}

/**
 * This method executes the necessary steps to load the shader with uniforms. Please note,
 * that the optional parameter list must be of the sequence matrix, color, light
 */
void ShaderManagementUnit::PrepareShaders(HDProgramTypes shaderProgIndex,...){

	GLuint shaderProgID = 0; //Handle to the shader program

	if(shaderProgIndex >= HD_SHADER_ENDLIST || shaderHandles[shaderProgIndex] == 0){
		__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "The shaderHandle provided does not exist or was not loaded properly.");

		return;
	}else{
		shaderProgID = shaderHandles[shaderProgIndex];
		glUseProgram(shaderProgID);
	}

	//Prepare the optional-parameter list for access
	va_list optAttr;
	va_start(optAttr,shaderProgIndex);

	//Start the shader Program
	//glUseProgram(shaderProgID);
	//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Shader-Program successfully started");

	switch(shaderProgIndex){

	case HD_SHADER_UNIFORM_COLOR:{

		//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Preparing the Uniform_Color Shader");

		//Setup the Matrix and the color-Vector for rendering.
		GLint matrixHandle, colorHandle;

			//Get the handle to the matrix-attribute
			matrixHandle = glGetUniformLocation(shaderProgID,"HMvp");
			colorHandle = glGetUniformLocation(shaderProgID,"HColor");

			H44DMatrix* matrixAttr = va_arg(optAttr, H44DMatrix*);
				glUniformMatrix4fv(matrixHandle,1,GL_FALSE, *matrixAttr);

			H4DVector* colorAttr = va_arg(optAttr, H4DVector*);
				glUniform4fv(colorHandle,1, *colorAttr);
		}
		break;

	case HD_SHADER_PRIMITIVE_SHADING:{

		//Setup the Matrix-handle for rendering.
		GLint matrixHandle;

			//Get the handle to the matrix-attribute
			matrixHandle = glGetUniformLocation(shaderProgID,"HMvp");

			H44DMatrix* matrixAttr = va_arg(optAttr, H44DMatrix*);
				glUniformMatrix4fv(matrixHandle,1,GL_FALSE, *matrixAttr);
		}
		break;

	case HD_SHADER_POINT_LIGHT:{

		//As this shader requires quite a lot of uniforms, we will need a few more handles than usual
		GLint mvpHandle, mvHandle, colorHandle, lightHandle;

			//Get the handle to the matrix-attribute
			mvpHandle = glGetUniformLocation(shaderProgID,"HMvp");
			mvHandle = glGetUniformLocation(shaderProgID,"HMvM");
			colorHandle = glGetUniformLocation(shaderProgID,"HColor");
			lightHandle = glGetUniformLocation(shaderProgID,"HLightSpot");

			H44DMatrix* mvpAttr = va_arg(optAttr, H44DMatrix*);
				glUniformMatrix4fv(mvpHandle,1,GL_FALSE, *mvpAttr);

			H44DMatrix* mvAttr = va_arg(optAttr, H44DMatrix*);
				glUniformMatrix4fv(mvHandle,1,GL_FALSE, *mvAttr);

			H4DVector* colorAttr = va_arg(optAttr, H4DVector*);
				glUniform4fv(colorHandle,1, *colorAttr);

			H3DVector* ligthAttr = va_arg(optAttr, H3DVector*);
				glUniform3fv(lightHandle,1, *ligthAttr);
		}
		break;
	}

	va_end(optAttr);


	CheckGlError("PrepareShaders");
}


